package com.proj.service;

import com.dao.CollectionGoodsDao;
import com.dao.FootprintDao;
import com.dao.GoodsDao;
import com.dao.OrderDao;
import com.dao.UserDao;
import com.entity.Goods;
import com.proj.dao.RecommendDao;
import com.proj.model.RecommendType;
import com.proj.model.RecommendUser;
import com.utils.SqlSessionGetter;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 实现和推荐相关的功能
 *
 * 
 */
@Service
public class RecommendService {
  /**
   * 按用户浏览历史推荐书籍（通过浏览历史，查看用户查看的书籍的种类前几名，然后推荐前几名种类的点击量最高的N本书籍）
   *
   * @param userId 用户编号
   * @param categoryNum 种类个数
   * @param goodsNum 图书本数
   * @return
   */
  public List<Goods> getGoodsByCategoryIdsFromHistory(
      String userId, int categoryNum, int goodsNum) {
    SqlSession session = SqlSessionGetter.getSqlSession();
    RecommendDao recommendDao = session.getMapper(RecommendDao.class);
    List<Goods> goodsList =
        recommendDao.selectGoodsByCategoryIdsFromHistory(userId, categoryNum, goodsNum);
    session.close();
    return goodsList;
  }

  /**
   * 推荐点击量前N名的书籍
   *
   * @param goodsNum 图书本数
   * @return
   */
  public List<Goods> getGoodsByVisit(int goodsNum) {
    SqlSession session = SqlSessionGetter.getSqlSession();
    RecommendDao recommendDao = session.getMapper(RecommendDao.class);

    List<Goods> goodsList = recommendDao.selectGoodsByVisit(goodsNum);
    session.close();
    return goodsList;
  }

  /**
   * 推荐收藏最多的N本书
   *
   * @param goodsNum 图书本数
   * @return
   */
  public List<Goods> getGoodsByCollection(int goodsNum) {
    SqlSession session = SqlSessionGetter.getSqlSession();
    RecommendDao recommendDao = session.getMapper(RecommendDao.class);

    List<Goods> goodsList = recommendDao.selectGoodsByCollection(goodsNum);
    session.close();
    return goodsList;
  }

  /**
   * 浏览过该书籍的顾客也浏览过的书籍
   *
   * @param goodsNum 图书本数
   * @param goodsId 图书编号
   * @return
   */
  public List<Goods> getGoodsByGoodsFromHistory(int goodsNum, String goodsId) {
    SqlSession session = SqlSessionGetter.getSqlSession();
    RecommendDao recommendDao = session.getMapper(RecommendDao.class);

    List<Goods> goodsList = recommendDao.selectGoodsByGoodsFromHistory(goodsNum, goodsId);
    session.close();
    return goodsList;
  }

  /**
   * 购买过该书籍的顾客也购买过的书籍
   *
   * @param goodsNum 图书本数
   * @param goodsId 图书编号
   * @return
   */
  public List<Goods> getGoodsByGoodsFromOrder(int goodsNum, String goodsId) {
    SqlSession session = SqlSessionGetter.getSqlSession();
    RecommendDao recommendDao = session.getMapper(RecommendDao.class);

    List<Goods> goodsList = recommendDao.selectGoodsByGoodsFromOrder(goodsNum, goodsId);
    session.close();
    return goodsList;
  }

  /**
   * 查找出每个用户对不同分类的喜爱值
   *
   * @return
   */
  public List<RecommendUser> findFavorType() {
    // 存放用户ID、商品ID、喜爱程度
    List<Map<String, Object>> userGoodsScore = new ArrayList<>();

    // 存放用户ID、类别ID、喜爱程度
    List<Map<String, Object>> userTypeScore = new ArrayList<>();

    List<RecommendUser> recommendUserList = new ArrayList<>();

    SqlSession session = SqlSessionGetter.getSqlSession();

    // 获取用户ID列表
    UserDao userDao = session.getMapper(UserDao.class);
    List<String> userList = userDao.selectAllUserIds();

    FootprintDao footprintDao = session.getMapper(FootprintDao.class);
    CollectionGoodsDao collectionGoodsDao = session.getMapper(CollectionGoodsDao.class);
    OrderDao orderDao = session.getMapper(OrderDao.class);
    GoodsDao goodsDao = session.getMapper(GoodsDao.class);

    for (String userId : userList) {
      RecommendUser recommendUser = new RecommendUser(userId);

      // 查找出用户浏览过的书籍
      List<Map<String, Object>> visitGoodsList = footprintDao.selectFootprint(userId);

      // 查找出用户收藏过的书籍
      List<String> collectionGoodsList = collectionGoodsDao.selectCollectionGoods(userId);

      // 查找处用户购买过的书籍
      List<String> buyGoodsList = orderDao.selectGoodsOnOrder(userId);

      for (Map<String, Object> map : visitGoodsList) {
        Map<String, Object> map1 = new HashMap<>(3);
        map1.put("userId", userId);
        String goodsId = (String) map.get("goodId");
        map1.put("goodId", goodsId);
        long score = (long) map.get("num") * 4;
        if (score > 20) {
          score = 20;
        }
        map1.put("score", score);
        userGoodsScore.add(map1);
      }

      for (String goodId : collectionGoodsList) {
        String tempGoodId = goodId;
        int index = isRepeat(userGoodsScore, tempGoodId, userId, "goodId");
        if (index != -1) {
          Integer tempScore = Integer.parseInt(userGoodsScore.get(index).get("score").toString());
          userGoodsScore.get(index).put("score", tempScore + 30);
        } else {
          Map<String, Object> map1 = new HashMap<>(3);
          map1.put("userId", userId);
          map1.put("goodId", tempGoodId);
          map1.put("score", 30);
          userGoodsScore.add(map1);
        }
      }

      for (String goodId : buyGoodsList) {
        String tempGoodId = goodId;
        int index = isRepeat(userGoodsScore, tempGoodId, userId, "goodId");
        if (index != -1) {
          Integer tempScore = Integer.parseInt(userGoodsScore.get(index).get("score").toString());
          userGoodsScore.get(index).put("score", tempScore + 50);
        } else {
          Map<String, Object> map1 = new HashMap<>(3);
          map1.put("userId", userId);
          map1.put("goodId", tempGoodId);
          map1.put("score", 50);
          userGoodsScore.add(map1);
        }
      }

      for (Map<String, Object> map : userGoodsScore) {
        Integer tempTypeId = goodsDao.selectGoodType((String) map.get("goodId"));
        int index = isRepeat(userTypeScore, tempTypeId, userId, "typeId");
        if (index != -1) {
          Integer tempScore = Integer.parseInt(userTypeScore.get(index).get("score").toString());
          userTypeScore
              .get(index)
              .put("score", tempScore + Integer.parseInt(map.get("score").toString()));
          recommendUser.find(tempTypeId).score =
              tempScore + Integer.parseInt(map.get("score").toString());
        } else {
          Map<String, Object> map1 = new HashMap<>(3);
          map1.put("userId", userId);
          map1.put("typeId", tempTypeId);
          map1.put("score", map.get("score"));
          userTypeScore.add(map1);
          recommendUser.set(tempTypeId, Integer.parseInt(map.get("score").toString()));
        }
      }
      recommendUserList.add(recommendUser);
    }
    for (RecommendUser recommendUser : recommendUserList) {
      recommendUser
          .getRecommendTypeList()
          .sort(
              new Comparator<RecommendType>() {
                @Override
                public int compare(RecommendType o1, RecommendType o2) {
                  return o1.score > o2.score ? -1 : 1;
                }
              });
    }
    session.close();
    return recommendUserList;
  }

  public int isRepeat(List<Map<String, Object>> maps, Object id, String userId, String repeatType) {
    int index = 0;
    for (Map<String, Object> map : maps) {
      if (id.equals(map.get(repeatType)) && userId.equals((String) map.get("userId"))) {
        return index;
      }
      index++;
    }
    return -1;
  }

  private double pearson_dis(List<RecommendType> rating1, List<RecommendType> rating2) {
    //        int n = rating1.size();
    //        List<Integer> rating1ScoreCollect = rating1.stream().map(A ->
    // A.score).collect(Collectors.toList());
    //        List<Integer> rating2ScoreCollect = rating2.stream().map(A ->
    // A.score).collect(Collectors.toList());
    //
    //        double Ex = rating1ScoreCollect.stream().mapToDouble(x -> x).sum();
    //        double Ey = rating2ScoreCollect.stream().mapToDouble(y -> y).sum();
    //        double Ex2 = rating1ScoreCollect.stream().mapToDouble(x -> Math.pow(x, 2)).sum();
    //        double Ey2 = rating2ScoreCollect.stream().mapToDouble(y -> Math.pow(y, 2)).sum();
    //        double Exy= IntStream.range(0,n).mapToDouble(i->rating1ScoreCollect.get(i) *
    // rating2ScoreCollect.get(i)).sum();
    //        double numerator = Exy - Ex * Ey / n;
    //        double denominator = Math.sqrt((Ex2-Math.pow(Ex, 2) / n) * (Ey2 - Math.pow(Ey, 2) /
    // n));
    //        if (denominator == 0) {
    //            return 0.0;
    //        }
    //        return numerator/denominator;
    try {
      if (rating1.size() != rating2.size()) { // 两个变量的观测值是成对的，每对观测值之间相互独立。
        if (rating1.size() > rating2.size()) { // 保留小的处理大
          List<RecommendType> temp = rating1;
          rating1 = new ArrayList<>();
          for (int i = 0; i < rating2.size(); i++) {
            rating1.add(temp.get(i));
          }
        } else {
          List<RecommendType> temp = rating2;
          rating2 = new ArrayList<>();
          for (int i = 0; i < rating1.size(); i++) {
            rating1.add(temp.get(i));
          }
        }
      }
      double sim = 0D; // 最后的皮尔逊相关度系数
      double commonItemsLen = rating1.size(); // 操作数的个数
      double oneSum = 0D; // 第一个相关数的和
      double twoSum = 0D; // 第二个相关数的和
      double oneSqSum = 0D; // 第一个相关数的平方和
      double twoSqSum = 0D; // 第二个相关数的平方和
      double oneTwoSum = 0D; // 两个相关数的乘积和
      for (int i = 0; i < rating1.size(); i++) { // 计算
        double oneTemp = rating1.get(i).score;
        double twoTemp = rating2.get(i).score;
        // 求和
        oneSum += oneTemp;
        twoSum += twoTemp;
        oneSqSum += Math.pow(oneTemp, 2);
        twoSqSum += Math.pow(twoTemp, 2);
        oneTwoSum += oneTemp * twoTemp;
      }
      double num = (commonItemsLen * oneTwoSum) - (oneSum * twoSum);
      double den =
          Math.sqrt(
              (commonItemsLen * oneSqSum - Math.pow(oneSum, 2))
                  * (commonItemsLen * twoSqSum - Math.pow(twoSum, 2)));
      sim = (den == 0) ? 1 : num / den;
      return sim;
    } catch (Exception e) {
      return 0.0;
    }
  }

  public List<Goods> recommendByUsers(String userId, int goodsNum) {
    // 查找用户喜欢的分类
    List<RecommendUser> recommendUserList = findFavorType();

    Map<Double, String> distances = new TreeMap<>();

    RecommendUser recommendUser1 = new RecommendUser(userId);
    for (RecommendUser recommendUser : recommendUserList) {
      if (userId.equals(recommendUser.getUserId())) {
        recommendUser1 = recommendUser;
      }
    }

    int listSize = recommendUserList.size();
    for (int i = 0; i < listSize; i++) {
      RecommendUser recommendUser2 = recommendUserList.get(i);
      if (!recommendUser2.getUserId().equals(userId)) {
        double distance =
            pearson_dis(
                recommendUser2.getRecommendTypeList(), recommendUser1.getRecommendTypeList());
        distances.put(distance, recommendUser2.getUserId());
      }
    }
    // 最近邻
    String nearest = distances.values().iterator().next();

    // 找到最近邻喜欢的分类，计算推荐
    RecommendUser neighborRatings = new RecommendUser();
    for (RecommendUser recommendUser : recommendUserList) {
      if (nearest.equals(recommendUser.getUserId())) {
        neighborRatings = recommendUser;
      }
    }

    neighborRatings
        .getRecommendTypeList()
        .sort(
            new Comparator<RecommendType>() {
              @Override
              public int compare(RecommendType o1, RecommendType o2) {
                return o1.score > o2.score ? -1 : 1;
              }
            });
    List<RecommendType> recommendTypeList = new ArrayList<>();
    for (int i = 0; i < neighborRatings.getRecommendTypeList().size() && i < 3; i++) {
      RecommendType recommendType =
          new RecommendType(
              neighborRatings.getRecommendTypeList().get(i).typeId,
              neighborRatings.getRecommendTypeList().get(i).score);
      recommendTypeList.add(recommendType);
    }

    List<Integer> categoryList = new ArrayList<>();
    for (RecommendType recommendType : recommendTypeList) {
      categoryList.add(recommendType.typeId);
    }

    SqlSession session = SqlSessionGetter.getSqlSession();
    GoodsDao goodsDao = session.getMapper(GoodsDao.class);

    List<Goods> goodsList;
    goodsList = goodsDao.selectGoodsByCategoryList(categoryList, goodsNum);

    session.close();
    return goodsList;
  }
}
